﻿using System;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace WolfGame {

  /// <summary>
  /// This class is used to represent the camera and control its movement for
  /// viewing the world.
  /// </summary>
  class v_Camera {

    #region Member Variables

    private Common common;
    private float chRadians;                                //for 1st person camera
    private float cvRadians;                                //for 1st person camera
    private float radius = 1f;

    #endregion

    #region Constructor

    /// <summary>
    /// Instantiate the class
    /// </summary>
    /// <param name="common"></param>
    public v_Camera(Common common) {
      Console.WriteLine("Initializing camera...");
      this.common = common;
      MoveDist = 1f;
    }

    #endregion

    #region Movement

    /// <summary>
    /// Set the initial position of the camera.
    /// </summary>
    /// <param name="cameraPosition">The start point of the camera</param>
    /// <param name="h">Horizontal radians</param>
    /// <param name="v">Vertical radians</param>
    public void setCamera(Vector3 cameraPosition, float h, float v) {
      CameraPosition = cameraPosition;
      // The camera target is going to be straight ahead along the z-axis.
      CameraTarget = new Vector3(0, 0, 0);
      CameraUpVector = new Vector3(0, 0, 1);

      MatProjection = Matrix.PerspectiveFovLH((float)Math.PI / 4, 1, 1f, 1000f);
      MatView = Matrix.LookAtLH(CameraPosition, CameraTarget, CameraUpVector);

      HRadians = h;
      VRadians = v;
      ConstVRadians = v;

      chRadians = h;
      cvRadians = v;

      rotateCamera((float)Math.PI, 0f);
    }

    /// <summary>
    /// Method which is called when the camera needs to be pivoted on the Z-axis.
    /// </summary>
    /// <param name="h">How much to pivot horizontally.</param>
    /// <param name="v">How much to pivot vertically.</param>
    public void rotateCamera(float h, float v) {
      HRadians += h;
      HRadians %= 2 * (float)Math.PI;
      if ((VRadians + v) > (0 - (float)Math.PI / 2) + .1f && (VRadians + v) < (float)Math.PI / 2 - .1f)
        VRadians += v;

      CameraTarget = new Vector3(
        CameraPosition.X + (float)(radius * Math.Cos(VRadians) * Math.Cos(HRadians)),
        CameraPosition.Y + (float)(radius * Math.Sin(VRadians)),
        CameraPosition.Z + (float)(radius * Math.Cos(VRadians) * Math.Sin(HRadians)));

      CameraUpVector = new Vector3(
        CameraPosition.X - CameraTarget.X,
        Math.Abs(CameraPosition.Y + (float)(radius * Math.Sin(VRadians + Math.PI / 2))),
        CameraPosition.Z - CameraTarget.Z);
    }

    /// <summary>
    /// Strafe the camera, or slide parallel to where the camera is facing
    /// </summary>
    /// <param name="h"></param>
    /// <param name="v"></param>
    public void slideCamera(float h, float v) {
      CameraPosition = new Vector3(
        CameraPosition.X + h * MoveDist * (float)Math.Cos(HRadians + Math.PI / 2),
        CameraPosition.Y + v * MoveDist,
        CameraPosition.Z + h * MoveDist * (float)Math.Sin(HRadians + Math.PI / 2));

      rotateCamera(0, 0);
    }

    /// <summary>
    /// Move the camera in the pointed direction, but don't change Y values
    /// </summary>
    /// <param name="h"></param>
    public void moveCameraWithGravity(float h) {
      CameraPosition = new Vector3(
        CameraPosition .X + h * MoveDist * (float)(Math.Cos(ConstVRadians) * Math.Cos(HRadians)),
        CameraPosition .Y,
        CameraPosition .Z + h * MoveDist * (float)(Math.Cos(ConstVRadians) * Math.Sin(HRadians)));
      rotateCamera(0, 0);
    }

    /// <summary>
    /// Move the camera forward or backward perpendicular to the direction it is facing
    /// </summary>
    /// <param name="d"></param>
    public void moveCamera(float d) {
      CameraPosition = new Vector3(
        CameraPosition .X + d * MoveDist * (float)(Math.Cos(VRadians) * Math.Cos(HRadians)),
        CameraPosition .Y + d * MoveDist * (float)Math.Sin(VRadians),
        CameraPosition .Z + d * MoveDist * (float)(Math.Cos(VRadians) * Math.Sin(HRadians)));
      rotateCamera(0, 0);
      //changeCamera();
    }

    /// <summary>
    /// After the camera moves, the view matrix needs to be updated, so this updates it
    /// </summary>
    public void changeCamera() {
      MatView = Matrix.LookAtLH(CameraPosition, CameraTarget, CameraUpVector);
    }

    /// <summary>
    /// Set the camera on the position of an entity
    /// </summary>
    /// <param name="position"></param>
    /// <param name="hRad"></param>
    /// <param name="vRad"></param>
    public void setCamOnEntity(Vector3 position, float hRad, float vRad) {
      CameraPosition = position;
      rotateCamera(hRad, vRad);
    }

    /// <summary>
    /// Center the camera in the Y direction
    /// </summary>
    public void centerView() {
      float diffH, diffV;

      //diffH = chRadians - hRadians;
      diffH = 0;
      diffV = cvRadians - VRadians;

      rotateCamera(diffH, diffV);
    }

    #endregion

    #region Properties

    public Vector3 CameraPosition { get; set; }
    public Vector3 CameraTarget { get; set; }
    public Vector3 CameraUpVector { get; set; }
    public float MoveDist { get; set; }
    public float HRadians { get; set; }
    public float VRadians { get; set; }
    public float ConstVRadians { get; set; }
    public Matrix MatView { get; set; }
    public Matrix MatProjection { get; set; }

    #endregion
  }
}
